#include "KmdHost.h"
#include "common.h"
#include "Utils.h"

#include "DisplayGdi.h"
#include "KeypadWin32.h"
#include "MouseWin32.h"

KmdHost::KmdHost() : _hwnd(0), _is_bind(0), _pbkdisplay(nullptr), _bkmouse(new MouseWin32), _keypad(new KeypadWin32), _display(0), _mode(0)
{
    _display_method = std::make_pair<wstring, wstring>(L"screen", L"");
}

KmdHost::~KmdHost()
{
    /*_hwnd = NULL;
    _is_bind = 0;
    _mode = 0;
    _bkmouse->UnBind();
    if (_pbkdisplay) {
        _pbkdisplay->UnBind();
        delete _pbkdisplay;
        _pbkdisplay = nullptr;
    }*/
    UnBindWindow();
    SAFE_DELETE(_bkmouse);
    SAFE_DELETE(_keypad);
}

long KmdHost::BindWindow(long hwnd, const wstring &sdisplay, const wstring &smouse, const wstring &skeypad, long mode)
{
    // step 1.避免重复绑定
    UnBindWindow();

    HWND hWnd = hwnd == 0 ? GetDesktopWindow() : HWND(hwnd);
    // step 2.check hwnd
    if (!::IsWindow(hWnd))
    {
        setlog(L"Invalid window handles");
        return 0;
    }

    int display, mouse, keypad;
    // step 3.check display... mode
    if (sdisplay == L"normal")
        display = RDT_NORMAL;
    else if (sdisplay == L"normal.dxgi")
        display = RDT_NORMAL_DXGI;
#ifdef _WIN32_WINNT_WIN11
    else if (sdisplay == L"normal.wgc")
        display = RDT_NORMAL_WGC;
#endif
    else if (sdisplay == L"gdi")
        display = RDT_GDI;
    else if (sdisplay == L"gdi2")
        display = RDT_GDI2;
    else if (sdisplay == L"dx2")
        display = RDT_GDI_DX2;
    else if (sdisplay == L"dx")
        display = RDT_DX_DEFAULT;
    else if (sdisplay == L"dx.d3d9")
        display = RDT_DX_D3D9;
    else if (sdisplay == L"dx.d3d10")
        display = RDT_DX_D3D10;
    else if (sdisplay == L"dx.d3d11")
        display = RDT_DX_D3D11;
    else if (sdisplay == L"dx.d3d12")
        display = RDT_DX_D3D12;
    else if (sdisplay == L"opengl")
        display = RDT_GL_DEFAULT;
    else if (sdisplay == L"opengl.std")
        display = RDT_GL_STD;
    else if (sdisplay == L"opengl.nox")
        display = RDT_GL_NOX;
    else if (sdisplay == L"opengl.es")
        display = RDT_GL_ES;
    else if (sdisplay == L"opengl.fi") // glFinish
        display = RDT_GL_FI;
    else
    {
        setlog(L"error display mode: %s", sdisplay.c_str());
        return 0;
    }
    // check mouse
    if (smouse == L"normal")
        mouse = INPUT_TYPE::IN_NORMAL;
    else if (smouse == L"normal.hd")
        mouse = INPUT_TYPE::IN_NORMAL2;
    else if (smouse == L"normal.wd")
        mouse = INPUT_TYPE::IN_NORMAL3;
    else if (smouse == L"windows")
        mouse = INPUT_TYPE::IN_WINDOWS;
    else if (smouse == L"dx")
        mouse = INPUT_TYPE::IN_DX;
    else
    {
        setlog(L"error mouse mode: %s", smouse.c_str());
        return 0;
    }
    // check keypad
    if (skeypad == L"normal")
        keypad = INPUT_TYPE::IN_NORMAL;
    else if (skeypad == L"normal.hd")
        keypad = INPUT_TYPE::IN_NORMAL2;
    else if (skeypad == L"normal.wd")
        keypad = INPUT_TYPE::IN_NORMAL3;
    else if (skeypad == L"windows")
        keypad = INPUT_TYPE::IN_WINDOWS;
    else
    {
        setlog(L"error keypad mode: %s", sdisplay.c_str());
        return 0;
    }
    if (mouse == INPUT_TYPE::IN_NORMAL3 || keypad == INPUT_TYPE::IN_NORMAL3)
    {
    }
    // step 4.init
    _mode = mode;
    _display = display;
    _hwnd = (long)hWnd;
    set_display_method(L"screen");

    // step 5. create instance
    _pbkdisplay = createDisplay(display);
    _bkmouse = createMouse(mouse);
    _keypad = createKeypad(keypad);

    if (!_pbkdisplay || !_bkmouse || !_keypad)
    {
        setlog(L"create instance error!");
        UnBindWindow();
        return 0;
    }
    // step 6.try bind
    if (_pbkdisplay->Bind(_hwnd, display) != 1 ||
        _bkmouse->Bind(_hwnd, mouse) != 1 ||
        _keypad->Bind(_hwnd, keypad) != 1)
    {
        UnBindWindow();
        return 0;
    }

    // 等待线程创建好
    Sleep(200);

    _is_bind = 1;
    return 1;
}

long KmdHost::UnBindWindow()
{
    // to do
    // clear ....
    _hwnd = NULL;
    _is_bind = 0;
    _mode = 0;

    if (_pbkdisplay)
    {
        _pbkdisplay->UnBind();
        SAFE_DELETE(_pbkdisplay);
    }
    if (_bkmouse)
    {
        _bkmouse->UnBind();
        SAFE_DELETE(_bkmouse);
    }
    if (_keypad)
    {
        _keypad->UnBind();
        SAFE_DELETE(_keypad);
    }
    // 恢复为前台(默认)
    _bkmouse = new MouseWin32;
    _keypad = new KeypadWin32;

    return 1;
}

long KmdHost::GetBindWindow()
{
    return (long)_hwnd;
}

long KmdHost::IsBind()
{
    return _pbkdisplay ? 1 : 0;
}
long KmdHost::ResetWindow(long hwnd)
{
    _hwnd = (long)hwnd;
    // 暂时只重新绑定display
    if (_pbkdisplay == nullptr)
        _pbkdisplay = createDisplay(_display);
    _pbkdisplay->Bind(_hwnd, _display);
    return 1;
}

// long bkbase::GetCursorPos(int& x, int& y) {
//	POINT pt;
//	auto r = ::GetCursorPos(&pt);
//	x = pt.x; y = pt.y;
//	return r;
// }

long KmdHost::GetDisplay()
{
    return _display;
}

// byte* bkbase::GetScreenData() {
//	if (get_display_method().first == L"screen") {
//		return _pbkdisplay ? _pbkdisplay->get_data() : nullptr;
//	}
//	else {
//		if (get_display_method().first == L"pic") {
//			return _pic.pdata;
//		}
//		if (get_display_method().first == L"mem") {
//
// #if OP64==1
//			auto ptr= (byte*)_wtoi64(get_display_method().second.data());
// #else
//			auto ptr = (byte*)_wtoi(get_display_method().second.data());
// #endif //
//
//			auto pbfh = (BITMAPFILEHEADER*)ptr;
//			return ptr + pbfh->bfOffBits;
//	}
//		return nullptr;
// }
//
//
// }

long KmdHost::get_height()
{
    auto &displayMethod = get_display_method();
    if (displayMethod.first == L"pic")
    {
        return _pic.rows;
    }
    else if (displayMethod.first == L"mem")
    {
        auto strPtr = displayMethod.second;
#if OP64 == 1
        auto ptr = (byte *)_wtoi64(strPtr.data());
#else
        auto ptr = (byte *)_wtoi(strPtr.data());
#endif //

        auto bih = (BITMAPINFOHEADER *)(ptr + sizeof(BITMAPFILEHEADER));
        return bih->biHeight < 0 ? -bih->biHeight : bih->biHeight;
    }
    else
    {
        return _pbkdisplay ? _pbkdisplay->get_height() : 0;
    }
}

long KmdHost::get_width()
{
    auto &displayMethod = get_display_method();
    if (displayMethod.first == L"pic")
    {
        return _pic.cols;
    }
    else if (displayMethod.first == L"mem")
    {
        auto strPtr = displayMethod.second;
#if OP64 == 1
        auto ptr = (byte *)_wtoi64(strPtr.data());
#else
        auto ptr = (byte *)_wtoi(strPtr.data());
#endif //

        auto bih = (BITMAPINFOHEADER *)(ptr + sizeof(BITMAPFILEHEADER));
        return bih->biWidth;
    }
    else
    {
        return _pbkdisplay ? _pbkdisplay->get_width() : 0;
    }
}

long KmdHost::RectConvert(long &x1, long &y1, long &x2, long &y2)
{

    /*if (_pbkdisplay && (_display == RENDER_TYPE::NORMAL || _display == RENDER_TYPE::GDI)) {
        x1 += _pbkdisplay->_client_x; y1 += _pbkdisplay->_client_y;
        x2 += _pbkdisplay->_client_x; y2 += _pbkdisplay->_client_y;
    }*/

    x2 = std::min<long>(this->get_width(), x2);
    y2 = std::min<long>(this->get_height(), y2);
    if (x1 < 0 || y1 < 0 || x1 >= x2 || y1 >= y2)
    {
        setlog(L"invalid rectangle:%d %d %d %d", x1, y1, x2, y2);
        return 0;
    }
    // if (_pbkdisplay) {
    //	if (_display == RDT_NORMAL) {//cap rect
    //		_pbkdisplay->rect.left = x1;
    //		_pbkdisplay->rect.top = y1;
    //		_pbkdisplay->rect.right = x2;
    //		_pbkdisplay->rect.bottom = y2;
    //	}
    //	else {
    //		_pbkdisplay->rect.left = 0;
    //		_pbkdisplay->rect.top = 0;
    //		_pbkdisplay->rect.right = _pbkdisplay->get_width();
    //		_pbkdisplay->rect.bottom =_pbkdisplay->get_height();
    //	}
    //
    // }
    return 1;
}

long KmdHost::get_image_type()
{

    if (_display_method.first == L"pic")
        return 0;
    else if (_display_method.first == L"mem")
    {

        return 1;
    }
    else
    {
        switch (GET_RENDER_TYPE(_display))
        {
        case RENDER_TYPE::NORMAL:
            return -2;
        case RENDER_TYPE::GDI:
            return -1;
        case RENDER_TYPE::DX:
            return 0;
        case RENDER_TYPE::OPENGL:
            return -1;
        default:
            return 0;
        }
    }
}

bool KmdHost::check_bind()
{
    // 已绑定
    if (IsBind())
        return true;
    // 显示模式非屏幕 TODO
    // if (get_display_method().first != L"screen")
    // {
    //     if (get_display_method().first == L"pic")
    //     { // load pic first
    //         wstring fullpath;
    //         if (Path2GlobalPath(get_display_method().second, currPath_, fullpath))
    //         {
    //             _pic.read(fullpath.data());
    //         }
    //     }
    //     return true;
    // }

    // 绑定前台桌面
    return BindWindow((long)::GetDesktopWindow(), L"normal", L"normal", L"normal", 0);
}

const std::pair<wstring, wstring> &KmdHost::get_display_method() const
{
    return _display_method;
}

long KmdHost::set_display_method(const wstring &method)
{
    if (method == L"screen")
    {
        _display_method.first = method;
        _display_method.second.clear();
        return 1;
    }
#if 0
    else
    {
        auto idx = method.find(L"pic:");
        if (idx != wstring::npos)
        {
            _display_method.first = L"pic";
            _display_method.second = method.substr(idx + 4);
            _pic.read(_display_method.second.data());
            return 1;
        }
        idx = method.find(L"mem:");
        if (idx != wstring::npos)
        {
            auto strPtr = method.substr(idx + 4);
#if OP64 == 1
            auto ptr = (byte *)_wtoi64(strPtr.data());
#else
            auto ptr = (byte *)_wtoi(strPtr.data());
#endif //

            if (ptr == nullptr)
            {
                return 0;
            }
            BITMAPFILEHEADER bfh = {0}; // bmp file header
            BITMAPINFOHEADER bih = {0}; // bmp info header
            memcpy(&bfh, ptr, sizeof(bfh));
            memcpy(&bih, ptr + sizeof(bfh), sizeof(bih));

            if (bfh.bfType != static_cast<WORD>(0x4d42))
                return 0;

            if (bih.biHeight < 0)
            { // 正常拷贝
                int h = -bih.biHeight;
                _pic.create(bih.biWidth, h);
                /*setlog("mem w=%d h=%d chk=%d",
                    bih.biWidth, h,
                    _pic.size() * 4 == bih.biSizeImage ? 1 : 0);*/
                memcpy(_pic.pdata, ptr + sizeof(bfh) + sizeof(bih), _pic.size() * 4);
            }
            else
            { // 倒过来拷贝
                int h = bih.biHeight;
                _pic.create(bih.biWidth, h);
                for (int i = 0; i < h; i++)
                {
                    memcpy(_pic.ptr<uchar>(i),
                           ptr + sizeof(bfh) + sizeof(bih) + (h - 1 - i) * bih.biWidth * 4,
                           bih.biWidth * 4);
                }
            }
            _display_method.first = L"mem";
            _display_method.second = strPtr;

            return 1;
        }
        return 0;
    }
#endif
}

bool KmdHost::requestCapture(int x1, int y1, int w, int h, cv::Mat &img)
{
    wstring method = get_display_method().first;
    if (method == L"screen")
        return _pbkdisplay->requestCapture(x1, y1, w, h, img);
#if 0
        else if (method == L"pic" || method == L"mem")
    {
        img.create(w, h);
        for (int i = 0; i < h; i++)
            memcpy(img.ptr<uchar>(i), _pic.ptr<uchar>(i + y1) + x1 * 4, w * 4);
        return true;
    }
#endif
    return false;
}

Display *KmdHost::createDisplay(int mode)
{
    Display *pDisplay = 0;

    if (mode == RDT_NORMAL || GET_RENDER_TYPE(mode) == RENDER_TYPE::GDI)
    {
        pDisplay = new DisplayGdi();
    }
#if 0
    else if (mode == RDT_NORMAL_DXGI)
    {
        pDisplay = new opDXGI();
    }
#ifdef _WIN32_WINNT_WIN11
    else if (mode == RDT_NORMAL_WGC)
    {
        pDisplay = new opWGC();
    }
#endif
    else if (GET_RENDER_TYPE(mode) == RENDER_TYPE::DX)
    {
        pDisplay = new opDxGL;
    }
    else if (GET_RENDER_TYPE(mode) == RENDER_TYPE::OPENGL)
        pDisplay = new opDxGL;
#endif
    else
        pDisplay = 0;
    return pDisplay;
}

Mouse *KmdHost::createMouse(int mode)
{
    if (mode == INPUT_TYPE::IN_NORMAL || mode == INPUT_TYPE::IN_WINDOWS || mode == INPUT_TYPE::IN_NORMAL3)
        return new MouseWin32();
#if 0
        else if (mode == INPUT_TYPE::IN_DX)
    {
        return new opMouseDx();
    }
#endif
    return nullptr;
}

Keypad *KmdHost::createKeypad(int mode)
{
    return new KeypadWin32();
}
